﻿/*
 * ThreadMediaReader.cpp
 *
 *  Created on: 2016-8-13
 *      Author: zhengboyuan
 */

#include "ThreadMediaReader.h"
#include "CMediaSourceFactory.h"
#include "FfmpegUtil.h"
#include "CFileMediaSink.h"
#include "CLog.h"
#include "MediaEvent.h"
#include "Utility.h"
#include "TimeHelper.h"
#include <errno.h>
#include "MediaConst.h"
#include "TFileUtil.h"
#include "BasicMacro.h"

#include "CMediaSourceFramer.h"
#include "SpsParser.h"
#include "HevcSpsParser.h"


namespace av
{


static const int MAX_SYNC_TIME = 10 * 1000 * 1000;
static const AVRational PREREAD_SCALE = av_make_q(5,2);

static const int MIN_DELAY_TIME = 5;	/// ms


int ThreadMediaReader::STACK_SIZE = 1024 * 1024;
int ThreadMediaReader::STREAM_BREAK_TIMEOUT = 1000 * 15;
size_t ThreadMediaReader::MAX_QUEUE_SIZE = 150;




ThreadMediaReader::ThreadMediaReader():
		m_isLive(true),
		m_state(STATE_STOPPED),
		m_refStreamIndex(),
		m_curTime(),
		m_scale(av_make_q(1,1)),
		m_endOfStream(),
		m_eventCallback(),
		m_eventContext(),
		m_handle(-1),
        m_streamActiveTime(),
        m_streamOpened(),
        m_checkstreamBreakTime()
{
	m_factory.reset(new CMediaSourceFactory());

	comn::Thread::setStackSize(STACK_SIZE);

	m_h26xPropDetector.setCodec(av::MEDIA_CODEC_H264);
}

ThreadMediaReader::~ThreadMediaReader()
{
	close();
}

int ThreadMediaReader::open(const std::string& url, const std::string& params)
{
	if (url.empty())
	{
		return EINVAL;
	}

	if (isOpen())
	{
		close();
	}

	m_url = url;
	m_params = params;

	m_endOfStream = false;

	assert(isRunning() == false);
	util::CommandThread::start();

	postCommand(kOpen);

	return 0;
}

void ThreadMediaReader::close()
{
	int state = getState();
	if (state != STATE_STOPPED)
	{
		stop();
	}

	if (isOpen())
	{
		postCommand(kClose);
	}

	util::CommandThread::stop();

	m_url.clear();

	m_endOfStream = false;
	m_isLive = true;
    m_streamOpened = false;
}

bool ThreadMediaReader::isOpen()
{
	return isRunning();
}

int ThreadMediaReader::getFormat(MediaFormat& fmt)
{
	if (!m_source)
	{
		return EAGAIN;
	}

	int ret = 0;
	comn::AutoCritSec lock(m_cs);
	fmt = m_format;
	if (!m_format.isValid())
	{
		ret = m_endOfStream ? EIO : EAGAIN;
	}
	return ret;
}

int ThreadMediaReader::getDuration()
{
	return m_format.m_duration;
}

int ThreadMediaReader::play()
{
	postCommand(kPlay);
	return 0;
}

int ThreadMediaReader::pause()
{
	postCommand(kPause);
	return 0;
}

void ThreadMediaReader::stop()
{
	postCommand(kStop);
}

int ThreadMediaReader::getState()
{
	return m_state;
}

bool ThreadMediaReader::seekable()
{
	return m_source && m_source->seekable();
}

int ThreadMediaReader::seek(int64_t offset)
{
	if (offset < 0)
	{
		return EINVAL;
	}

	{
		comn::AutoCritSec lock(m_cs);
		m_curTime = offset;
	}

	postCommand(kSeek, offset);
	return 0;
}

int64_t ThreadMediaReader::getTime()
{
	return m_curTime;
}

int ThreadMediaReader::setScale(float scale)
{
	if ((scale < MIN_SCALE) || (scale > MAX_SCALE))
	{
		return EINVAL;
	}

	AVRational r = av_d2q(scale, MAX_SCALE);

	postCommand(kSetScale, r.num, r.den);

	return 0;
}

float ThreadMediaReader::getScale()
{
	return (float)av_q2d(m_scale);
}

int ThreadMediaReader::read(AVPacketPtr& pkt)
{
    int rc = 0;
	pkt = m_pktQueue.pop();
    if (pkt)
    {
        m_checkstreamBreakTime = 0;
    }
    else
    {
        if (m_streamOpened)
        {
            if (m_endOfStream)
            {
                rc = EIO;
            }
            else
            {
                checkStreamBreak();
                rc = EAGAIN;
            }
        }
        else
        {
            timedwait(1);

            checkStreamBreak();

            rc = EAGAIN;
        }
	}
	return rc;
}

void ThreadMediaReader::interrupt()
{
	m_pktQueue.cancelWait();
}

bool ThreadMediaReader::isLive()
{
	return m_source && m_source->isLive();
}


int ThreadMediaReader::startRecord(const std::string& filename)
{
	if (filename.empty())
	{
		return EINVAL;
	}

	postCommand(kStartRecord, filename);
	return 0;
}

void ThreadMediaReader::stopRecord()
{
	postCommand(kStopRecord);
}

bool ThreadMediaReader::isRecording()
{
	comn::AutoCritSec lock(m_cs);
	return (m_fileSink.get() != NULL);
}


void ThreadMediaReader::onCommand(Command& cmd)
{
    CLog::debug("ThreadMediaReader::onCommand type: %d\n", cmd.type);

	if (cmd.type == kOpen)
	{
		handleOpen();
	}
	else if (cmd.type == kClose)
	{
		handleClose();
	}
	else if (cmd.type == kPlay)
	{
		handlePlay();
	}
	else if (cmd.type == kPause)
	{
		handlePause();
	}
	else if (cmd.type == kStop)
	{
		handleStop();
	}
	else if (cmd.type == kSeek)
	{
		int64_t offset = cmd.at(0).toLong();
		handleSeek(offset);
	}
	else if (cmd.type == kSetScale)
	{
        AVRational scale;
        scale.num = cmd.at(0).toInt();
        scale.den = cmd.at(1).toInt();

		handleSetScale(scale);

		fireMediaEvent(MEDIA_EVENT_SCALE, 0);
	}
	else if (cmd.type == kStep)
	{
		handleStep();
	}
	else if (cmd.type == kNextKeyFrame)
	{
		handleNextKeyFrame();
	}
	else if (cmd.type == kBackward)
	{
		int64_t offset = cmd.at(0).toLong();
		handleBackward(offset);
	}
	else if (cmd.type == kForward)
	{
		handleForward();
	}
	else if (cmd.type == kSnap)
	{
		std::string filePath = cmd.at(0).toString();
		handleSnap(filePath);
	}
	else if (cmd.type == kFlush)
	{
		handleFlush();
	}
	else if (cmd.type == kSetProp)
	{
		std::string key = cmd.at(0).toString();
		std::string value = cmd.at(1).toString();
		handleSetProp(key, value);
	}
	else if (cmd.type == kStartRecord)
	{
		std::string filePath = cmd.at(0).toString();
		handleStartRecord(filePath);
	}
	else if (cmd.type == kStopRecord)
	{
		handleStopRecord();
	}
	else if (cmd.type == kStartRender)
	{
		handleStartRender();
	}
	else if (cmd.type == kStopRender)
	{
		handleStopRender();
	}
	else if (cmd.type == kSetVideoWindow)
	{
		void* window = cmd.at(0).toPtr();
		handleSetVideoWindow(window);
	}
	else
	{

	}

}

void ThreadMediaReader::onIdle()
{
	if (!m_source)
	{
		timedwait(-1);
		return;
	}

	if (m_state != STATE_PLAYING)
	{
		timedwait(-1);
		return;
	}

	AVPacketPtr pkt(new AVPacket(), ffmpeg::PacketDeleter());
	av_init_packet(pkt.get());

	int rc = 0;
	
	if (m_sourceFramer)
	{
		rc = m_sourceFramer->read(pkt);
	}
	else
	{
		rc = m_source->read(*pkt.get());
	}

	if (rc == 0)
	{
		if (!m_isLive)
		{
			int ms = computeDelay(pkt);
			delay(ms);
		}

		tryDispatchPacket(pkt);

        m_streamActiveTime = av_gettime_relative() / 1000;
	}
	else if (rc == AVERROR_EOF)
	{
		fireMediaEvent(MEDIA_EVENT_END, 0);
		timedwait(1000 * 10);
	}
	else if (rc == AVERROR(EAGAIN))
	{
        if (m_isLive)
        {
            // continue;
            timedwait(50);

            checkStreamBreak();
        }
        else
        {
            //
        }
	}
	else
	{
		char errbuf[AV_ERROR_MAX_STRING_SIZE] = {0};
		av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, rc);

		CLog::info("ThreadMediaReader read error! code:%08x, err: %s, url:%s\n",
			rc, errbuf,
			m_url.c_str());

		fireMediaEvent(MEDIA_EVENT_END, 0);
		timedwait(1000 * 10);
	}
}

bool ThreadMediaReader::startup()
{
	return true;
}

void ThreadMediaReader::cleanup()
{
	while (fetchAndHandle())
	{
		// pass
	}
}

void ThreadMediaReader::doStop()
{
    util::CommandThread::doStop();

    if (m_source)
    {
        m_source->interrupt();
    }
}


void ThreadMediaReader::handleOpen()
{
	m_source.reset(m_factory->create(m_url, m_params), MediaSourceDeleter(m_factory));
	if (!m_source)
	{
        CLog::error("source is null.\n");
        return;
    }

    fireMediaEvent(MEDIA_EVENT_OPENING, 0);

	int rc = m_source->open(m_url, m_params);
	if (rc != 0)
	{
		fireMediaEvent(MEDIA_EVENT_OPEN_FAILED, rc);
		m_source.reset();
		return;
	}

    m_streamOpened = true;

    fireMediaEvent(MEDIA_EVENT_OPENED, 0);

	m_source->getFormat(m_format);
	m_isLive = m_source->isLive();

	m_refStreamIndex = m_format.hasVideo() ? MEDIA_TYPE_VIDEO : MEDIA_TYPE_AUDIO;

	if (((m_format.m_codec == av::MEDIA_CODEC_H264) || (m_format.m_codec == av::MEDIA_CODEC_H265)) && !isFormatReady())
	{
		m_h26xPropDetector.setCodec(m_format.m_codec);
	}
	else
	{
		fireMediaEvent(MEDIA_EVENT_FORMAT_READY, 0);
		fireMediaFormat(m_format);
	}

}

void ThreadMediaReader::handleClose()
{
	doCloseFile();
}

void ThreadMediaReader::handlePlay()
{
    if (!m_source)
    {
        return;
    }

    m_source->play();

	setState(STATE_PLAYING);

	resetTimePoint();

	fireMediaEvent(MEDIA_EVENT_PLAYING, 0);

    m_streamActiveTime = av_gettime_relative() / 1000;
}

void ThreadMediaReader::handlePause()
{
    if (!m_source)
    {
        return;
    }

    m_source->pause();

	setState(STATE_PAUSED);
	resetTimePoint();

	fireMediaEvent(MEDIA_EVENT_PAUSED, 0);
}

void ThreadMediaReader::handleStop()
{
	setState(STATE_STOPPED);

    if (m_source)
    {
	    m_source->stop();
    }

	fireMediaEvent(MEDIA_EVENT_STOPPED, 0);

    if (m_fileSink)
    {
        m_fileSink->close();
	    m_fileSink.reset();
    }

	m_scale = av_make_q(1,1);
    m_curTime = 0;

    resetTimePoint();
}

void ThreadMediaReader::handleSeek(int64_t offset)
{
	if (m_sourceFramer)
	{
		handleBackward(offset);
		return;
	}

	fireMediaEvent(MEDIA_EVENT_SEEKING, 0);

	int rc = m_source->seek(offset);
	if (rc != 0)
	{
		CLog::warning("failed to seek. offset:%lld", offset);
	}

	resetTimePoint();

	fireMediaEvent(MEDIA_EVENT_SEEKED, 0);

	handleFlush();

    if (m_format.hasVideo())
    {
        readUntilVideoKeyFrame();
    }
}

void ThreadMediaReader::handleSetScale(AVRational scale)
{
	resetTimePoint();

	m_scale = scale;
}

void ThreadMediaReader::handleStep()
{
	if (m_state == STATE_PLAYING)
	{
		handlePause();
	}

	readUntilVideoFrame();
}

void ThreadMediaReader::handleNextKeyFrame()
{
	if (m_state == STATE_PLAYING)
	{
		handlePause();
	}

	readUntilVideoKeyFrame();
}

void ThreadMediaReader::handleBackward(int64_t offset)
{
	int64_t pts = offset > 0 ? (offset * 1000) : m_curTime;

	m_sourceFramer.reset(new CMediaSourceFramer());
	m_sourceFramer->open(m_source, pts);
	handleFlush();

	setState(STATE_PLAYING);

	resetTimePoint();

	fireMediaEvent(MEDIA_EVENT_PLAYING, 0);
}

void ThreadMediaReader::handleForward()
{
	if(m_sourceFramer)
	{
		int64_t pts = m_sourceFramer->getTime();
		m_source->seek(pts / 1000);
	}

	m_sourceFramer.reset();
	handleFlush();

    if (m_source)
    {        
        readUntilVideoKeyFrame();
    }

	setState(STATE_PLAYING);

	resetTimePoint();

	fireMediaEvent(MEDIA_EVENT_PLAYING, 0);
}

void ThreadMediaReader::handleSnap(const std::string& filePath)
{
}

void ThreadMediaReader::handleFlush()
{
	m_pktQueue.clear();
}

void ThreadMediaReader::handleStartRecord(const std::string& filePath)
{
	if (!m_fileSink)
	{
		m_fileSink.reset(new CFileMediaSink());
        m_fileSink->open(filePath.c_str());

		if (m_format.isValid())
		{
			m_fileSink->onMediaFormat(m_format);
		}
	}
    else
    {
        m_fileSink->setFile(filePath.c_str());
    }
}

void ThreadMediaReader::handleStopRecord()
{
	m_fileSink.reset();
}

void ThreadMediaReader::handleSetProp(std::string& key, std::string& value)
{
//	std::string sec;
//	std::string subName;
//
//	comn::StringUtil::split(key, comn::IniProperties::SEPERATOR, sec, subName);
}

void ThreadMediaReader::handleStartRender()
{

}

void ThreadMediaReader::handleStopRender()
{

}

void ThreadMediaReader::handleSetVideoWindow(void* window)
{

}


void ThreadMediaReader::fireMediaEvent(int type, int64_t value)
{
	//if (type != MEDIA_EVENT_PACKET)
    CLog::info("media event. type:%d. name:%s\n", type, Utility::getEventName(type));

	{
		comn::AutoCritSec lock(m_cs);
		if (m_eventCallback)
		{
			(*m_eventCallback)(m_handle, type, value, m_eventContext);
		}
	}

	if (type == STREAM_EVENT_END)
	{
		comn::AutoCritSec lock(m_cs);
		if (m_fileSink)
		{
			m_fileSink->onMediaEvent(type);
		}

		m_endOfStream = true;
	}
	else if (type == MEDIA_EVENT_OPEN_FAILED)
	{
		comn::AutoCritSec lock(m_cs);
		m_endOfStream = true;
	}

}


void ThreadMediaReader::fireMediaFormat(const MediaFormat& fmt)
{
	comn::AutoCritSec lock(m_cs);
	if (m_fileSink)
	{
		m_fileSink->onMediaFormat(fmt);
	}

}

void ThreadMediaReader::fireMediaPacket(AVPacketPtr& pkt)
{
	if (pkt->stream_index == 0)
	{
		int64_t clock = av_gettime();
		int64_t pts = pkt->dts;
		util::TimestampChecker::Result lastResult;
		util::TimestampChecker::Result startResult;

		m_tsChecker.input(clock, pts, &lastResult, &startResult);

		int clockElapse = (int)lastResult.clockDiff/1000;
		int offset = (int)startResult.offset / 1000;

		//CLog::debug("video packet. clock: %lld, pts: %lld, elapse: %d, offset: %d, \n", clock/1000, pts/1000, clockElapse, offset);
	}

	//CLog::debug("fireMediaPacket. type: %d, size: %5d, flags: %d\n", pkt->stream_index, pkt->size, pkt->flags);

	int64_t dist = pkt->pts - m_curTime;

	m_curTime = pkt->pts;

	if (dist/1000 > 500)
	{
		const char* name = (pkt->stream_index == 0) ? "video" : "audio";
		CLog::debug("ThreadMediaReader(%p) packet(%s). duration:%d, dts:%lld, pts:%lld, dist:%d.\n",
				this, name, (int)(pkt->duration/1000), pkt->dts/1000, pkt->dts/1000, (int)(dist/1000));
	}

	//fireMediaEvent(MEDIA_EVENT_PACKET, m_pktQueue.size());

	/// 
	if ((pkt->stream_index == 0) &&
		((pkt->flags & AV_PKT_FLAG_KEY) != 0))
	{
		//size_t dropped = m_pktQueue.dropUntilKeyFrame();
		if (m_pktQueue.size() > MAX_QUEUE_SIZE)
		{
			size_t dropped = m_pktQueue.reserveFront(2);
			if (dropped > 0)
			{
				CLog::info("ThreadMediaReader queue:%d, dropped:%d\n", m_pktQueue.size(), dropped);
			}
		}

		m_pktQueue.push(pkt);
	}
	else
	{
		m_pktQueue.push(pkt);
	}

	comn::AutoCritSec lock(m_cs);
	if (m_fileSink)
	{
		m_fileSink->onMediaPacket(pkt);
	}

}


void ThreadMediaReader::doCloseFile()
{
	if (m_source)
	{
		m_source->close();
        m_source.reset();

		fireMediaEvent(MEDIA_EVENT_CLOSED, 0);
	}

	m_isLive = false;

}

void ThreadMediaReader::setState(StreamState state)
{
	comn::AutoCritSec lock(m_cs);
	m_state = state;
}

void ThreadMediaReader::dispatchPacket(AVPacketPtr& pkt)
{
	fireMediaPacket(pkt);
}


int ThreadMediaReader::computeDelay(AVPacketPtr& pkt)
{
	int64_t delay = 0;

	if (pkt->stream_index != m_refStreamIndex)
	{
		return 0;
	}

	int64_t curClock = getClock();
	TimePoint curPoint(curClock, pkt->dts);

	if (!m_startPoint.isSet())
	{
		m_startPoint = curPoint;
		m_lastPoint = curPoint;
	}
	else
	{
		int64_t offset = curPoint.getOffset(m_startPoint);

		int64_t ptsDelta = curPoint.m_pts - m_lastPoint.m_pts;
		int64_t clockDelta = curPoint.m_clock - m_lastPoint.m_clock;

		if (ptsDelta > MAX_SYNC_TIME)
		{
			CLog::info("----resetTimePoint %lld\n", ptsDelta / 1000);
			resetTimePoint();
		}
		else if (ptsDelta < 0)
		{
			resetTimePoint();
		}
		else
		{
			delay = offset;
		}

		m_lastPoint = curPoint;
	}

	//const char* name = (pkt->stream_index == 0) ? "video" : "audio";
	//CLog::debug("computeDelay. packet(%s) clock:%I64d, pts:%I64d, delay:%d\n", name, curPoint.m_clock/1000, curPoint.m_pts/1000, delay/1000);

	return (int)delay/1000;
}

int64_t ThreadMediaReader::getClock()
{
    int64_t clk = util::TimeHelper::getClock();
    if ((m_scale.num > 0) && (m_scale.num != m_scale.den))
    {
        clk = av_rescale(clk, m_scale.num, m_scale.den);
    }
    return clk;
}


void ThreadMediaReader::setEventCallback(ReaderEventCallback cb, void* context)
{
	CLog::info("setEventCallback %p,%p\n", cb, context);

	comn::AutoCritSec lock(m_cs);
	m_eventCallback = cb;
	m_eventContext = context;
}

void ThreadMediaReader::setHandle(int handle)
{
	m_handle = handle;
}

bool ThreadMediaReader::waitForReadable(int ms)
{
	if (m_pktQueue.size() > 0)
	{
		return true;
	}
	return m_pktQueue.timedwait(ms);
}

int ThreadMediaReader::step()
{
	postCommand(kStep);
	return 0;
}

int ThreadMediaReader::nextKeyFrame()
{
	postCommand(kNextKeyFrame);
	return 0;
}

int ThreadMediaReader::backward(int64_t offset)
{
	postCommand(kBackward, offset);
	return 0;
}

int ThreadMediaReader::forward()
{
	postCommand(kForward);
	return 0;
}

void ThreadMediaReader::delay(int ms)
{
	if (ms < MIN_DELAY_TIME)
	{
		return;
	}

	timedwait(ms);
}

void ThreadMediaReader::resetTimePoint()
{
	comn::AutoCritSec lock(m_cs);

	m_startPoint.reset();
	m_lastPoint.reset();
}


bool ThreadMediaReader::readUntilVideoFrame()
{
	bool found = false;
	while (!found)
	{
		AVPacketPtr pkt(new AVPacket(), ffmpeg::PacketDeleter());
		av_init_packet(pkt.get());

		int rc = m_source->read(*pkt.get());
		if (rc == 0)
		{
			if (pkt->stream_index == MEDIA_TYPE_VIDEO)
			{
				found = true;
			}

			dispatchPacket(pkt);
		}
		else if (rc == AVERROR_EOF)
		{
			break;
		}
	}

	return	found;
}

bool ThreadMediaReader::readUntilVideoKeyFrame()
{
	bool found = false;
    if (!m_source)
    {
        return false;
    }

	while (!found)
	{
		AVPacketPtr pkt(new AVPacket(), ffmpeg::PacketDeleter());
		av_init_packet(pkt.get());

		int rc = m_source->read(*pkt.get());
		if (rc == 0)
		{
            if (pkt->stream_index == MEDIA_TYPE_VIDEO)
            {
                if ((pkt->flags & AV_PKT_FLAG_KEY) != 0)
                {
                    dispatchPacket(pkt);
                    found = true;
                }
            }
		}
		else if (rc == AVERROR_EOF)
		{
			break;
		}
	}

	return	found;
}

bool ThreadMediaReader::isFormatReady()
{
	if (m_format.hasVideo())
	{
		return m_format.m_width > 0;
	}
	return true;
}

void ThreadMediaReader::tryDispatchPacket(AVPacketPtr& pkt)
{
	if (m_format.hasVideo() && m_format.m_width <= 0)
	{
		if (pkt->stream_index == av::MEDIA_TYPE_VIDEO && ((pkt->flags & AV_PKT_FLAG_KEY) != 0))
		{
			bool isKeyFrame = false;
			int naluType = 0;
			if (m_h26xPropDetector.detect(pkt->data, pkt->size, isKeyFrame, naluType))
			{
				std::string sprop = m_h26xPropDetector.getProp();
				m_format.setVideoProp(sprop.c_str(), sprop.size());

                if (m_format.m_codec == MEDIA_CODEC_H264)
                {
                    std::string sps = m_h26xPropDetector.getSps();
                    SpsParser::h264_sps_t h264sps = SpsParser::h264_sps_t();
                    if (SpsParser::parse((const uint8_t*)sps.c_str() + 1, sps.size() - 1, h264sps))
                    {
                        m_format.m_width = h264sps.width;
                        m_format.m_height = h264sps.height;
                        m_format.m_profile = h264sps.profile;

                        fireMediaEvent(MEDIA_EVENT_FORMAT_READY, 0);
                        fireMediaFormat(m_format);
                    }
                }
                else if (m_format.m_codec == MEDIA_CODEC_H265)
                {
                    std::string sps = m_h26xPropDetector.getSps();
                    HevcSpsParser::vc_params_t vcParams = HevcSpsParser::vc_params_t();
                    if (HevcSpsParser::parse((const uint8_t*)sps.c_str(), sps.size(), vcParams))
                    {
                        m_format.m_width = vcParams.width;
                        m_format.m_height = vcParams.height;
                        m_format.m_profile = vcParams.profile;

                        fireMediaEvent(MEDIA_EVENT_FORMAT_READY, 0);
                        fireMediaFormat(m_format);
                    }
                }
			}
		}
		else
		{
			return;
		}
	}

	dispatchPacket(pkt);
}

void ThreadMediaReader::checkStreamBreak()
{
    if (STREAM_BREAK_TIMEOUT > 0)
    {
        int64_t curClock = av_gettime_relative() / 1000;
        int64_t duration = 0;
		
        if (m_streamActiveTime > 0)
        {
            duration = (curClock - m_streamActiveTime);
        }
        else if (m_checkstreamBreakTime > 0)
        {
            duration = (curClock - m_checkstreamBreakTime);
        }
        else
        {
            m_checkstreamBreakTime = curClock;
        }

        if (duration > STREAM_BREAK_TIMEOUT)
        {
            if (m_streamActiveTime > 0)
            {
                m_streamActiveTime = curClock;
            }
            else
            {
                m_checkstreamBreakTime = curClock;
            }

            if (m_state == STATE_PLAYING)
            {
                fireMediaEvent(MEDIA_EVENT_STREAM_BREAK, 0);
            }
        }
    }
}

} /* namespace av */
